package com.ian.config;

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * Spring Security Config
 *
 * @author Witt
 * @version 1.0.0
 * @date 2022/4/30
 */

// @EnableWebSecurity: to enable Spring Security integration with Spring MVC
@EnableWebSecurity
/*
 * @EnableGlobalMethodSecurity 启用全局方法安全功能
 *   securedEnabled = true：启用注解 @Secured
 *   prePostEnabled = true：启用注解 @PreAuthorize(), @PostAuthorize(), @PreFilter(), @PostFilter()
 *   jsr250Enabled = true：启用JSR-250. These are standards-based and allow simple role-based constraints to be applied but do not have the power Spring Security’s native annotations.
 * note1: The annotated methods will only be secured for instances which are defined as Spring beans (in the same application context in which method-security is enabled). If you want to secure instances which are not created by Spring (using the new operator, for example) then you need to use AspectJ.
 * note2: You can enable more than one type of annotation in the same application, but only one type should be used for any interface or class as the behaviour will not be well-defined otherwise. If two annotations are found which apply to a particular method, then only one of them will be applied.
 */
// 启用注解 @PreAuthorize(), @PostAuthorize(), @PreFilter(), @PostFilter()
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
//@EnableMethodSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .mvcMatchers("/resources/**", "/signup", "/about").permitAll()
                .mvcMatchers("/db/**")
                .access("hasRole('ADMIN') and hasRole('DBA') and (hasAuthority('ADMIN') or isAnonymous() or isAuthenticated() or isFullyAuthenticated() or isRememberMe() or permitAll() or hasIpAddress('192.168.1.0/24') or @webSecurity.check(authentication,request))")
                .antMatchers("/hello1").anonymous()
                .antMatchers("/hello2", "/invalidSessionStrategy.html",
                        "invalidSessionUrl.html", "/logout", "/login?logout").permitAll()
                .antMatchers(HttpMethod.GET, "/user/**").hasRole("user")
                /*
                 the URL /admin will require the authenticated user to be an admin user. However, depending on our Spring MVC configuration, the URL /admin.html will also map to our admin() method. Additionally, depending on our Spring MVC configuration, the URL /admin/ will also map to our admin() method.
                 如果设置了ant匹配规则“/admin”，则不会拦截 “/admin/”，而这是一个漏洞.
                 SOLUTION1: 修改ant匹配规则为 “/admin/**”
                 SOLUTION2: 使用mvc匹配规则
                 */
//                .antMatchers(HttpMethod.GET, "/admin").hasRole("admin")
                .mvcMatchers(HttpMethod.GET, "/admin").hasRole("admin")
                .anyRequest().authenticated()
                .and()
                .rememberMe()
                .and()
                .formLogin(form -> form.loginPage("/login").loginProcessingUrl("/doLogin").permitAll())
                .logout(logout -> logout.permitAll())
//                .csrf().disable() // default: enabled
        ;
    }

    /**
     * In memory
     * @return UserDetailsService类型的对象
     */
    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
                User.builder()
                        .username("user")
                        // 密码明文是 123
                        .password("{bcrypt}$2a$10$EOWPKZDdcX5u1SYprJVDy.7puK7D/NVEEBdhutPCpJeicssrlLhJm")
                        .roles("user")
                        .build(),
                User.withUsername("admin")
                        // 密码明文是 123
                        .password("{bcrypt}$2a$10$EOWPKZDdcX5u1SYprJVDy.7puK7D/NVEEBdhutPCpJeicssrlLhJm")
                        .roles("admin")
                        .build());
    }
}
